数据包处理过程
链路层判断收到的数据包类型,提取数据包中的数据字段,记录主机物理地址信息;IP层根据数据包中的IP地址实现数据的存储和转发,根据数据包编号实现数据包的重装,提取数据包中关于传输层的信息,向上层递交数据包并记录递交结果;TCP使用数据包中的信息更新TCP状态机并向应用程序递交数据。
数据包管理
在协议栈内核中移动的数据包有很多,比如:首先从网卡上接收的原始数据包,它可以是包含TCP报文的长达数百字节的数据包,也可以是仅有几十字节的ARP数据包;对于要发送的数据包,上层应用可能将各种各样的数据递交给LwIP内核发送,这些数据可能存在于应用进程管理的内存空间内,也可能存在于外部flash中。
数据在各层之间传递时,LwIP极力避免数据的拷贝工作,因为这样会耗费大量的时间和内存。
数据包结构pbuf
数据包管理机构采用数据结构pbuf来描述协议栈中使用的数据包:
123456789struct pbuf {struct pbuf* next; // 构成pbuf链表时指向下一个pbuf结构void* payload; // 数据指针,指向pbuf所激励的数据领域u16_t tot_len; // 当前pbuf及其后续所有pbuf中包含的数据总长度u16_t len; // 当前pbuf的数据的长度u8_t type; // 当前pbuf的类型u8_t flags; // 状态位u16_t ref; // 指向该pbuf的指针数,即该pbuf被引用的次数};next:指向下一个pbuf结构,因为实际发送或接受的数据包可能很大,而每个pbuf能够管理的数据可能会有限,所以存在需要多个pbuf结构才能完全描述一个数据包的情况。
payload:数据指针,指向该pbuf管理的数据起始地址,这里数据起始地址可以是紧跟在pbuf结构之后的RAM空间中,也可能是ROM中的某个地址中,而决定这点的是当前pbuf的类型,即type字段的值。
len:当前pbuf中的有效数据长度,而tot_len表示当前pbuf和其后所有pbuf的有效数据的总长度,显然tot_len是len字段和pbuf链表中下一个pbuf的tot_len字段之和,pbuf链表中第一个pbuf的tot_len字段表示整个数据包的长度,而最后一个pbuf的tot_len字段必然同len字段相等。
type:表示pbuf的类型。
flags:通常设为0。
ref:pbuf被引用的次数,引用表示有其他指针指向当前buf,这里的指针可以是其他pbuf的next指针,也可以是其他形式的指针,初始化一个pbuf的时候,ref字段值被设置为1(因为该pbuf的地址一定会被返回给一个指针变量),当有其他指针指向该pbuf时,必须调用相关函数将pbuf的ref字段值增加。
pbuf的类型
有4中类型,分别是PBUF_RAM,PBUF_ROM,PBUF_REF和PBUF_POOL:
123456typedef enum {PBUF_RAM, // pbuf描述的数据在pbuf结构之后的连续内存堆中PBUF_ROM, // pbuf描述的数据在ROM中PBUF_REF, // pbuf描述的数据在RAM中,但位置与pbuf结构所处位置无关PBUF_POOL // pbuf结构与其描述的数据处于同一内存池中}pbuf_type;这4种类型中,PBUF_RAM类型的pbuf空间是通过内存堆分配得到的,使用最多,协议栈的待发送数据和应用程序的待发送数据一般都采用这个形式,申请PBUF_RAM类型的pbuf时,协议栈会在内存堆中分配相应空间,这里的大小包括如前所述的pbuf结构大小和相应缓冲区大小,它们在一片连续的内存堆存储区中。
1p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));通过这种方式分配的pbuf空间其pbuf结构和相应数据在一片连续的内存区域中,但是需要注意的是payload并没有指向整个数据区的起始处,而是间隔了一定区域,这段区域就是上面的offset,它用来存储数据包的各种首部字段,如TCP报文首部、IP首部、以太网帧首部等。
PBUF_POOL类型和PBUF_RAM类型的pbuf很类似,但它的空间是通过内存池分配得到的,这种类型的pbuf可以在极短的时间内得到分配,在网卡接收数据包时,我们就使用了这种方式包装数据。在申请PBUF_POOL类型pbuf时,协议栈会在内存池MEMP_PBUF_POOL中选择一个或多个POOL,以满足用户空间大小的申请,源代码是通过下面一条语句来完成POOL申请的,如果用户发送的数据很长,那么系统会多次调用上面的语句,为用户分配多个POOL。
1p = memp_malloc(MEMP_PBUF_POOL);
数据包申请函数
数据包申请函数pbuf_alloc在系统中许多地方会用到,例如在网卡接收数据时会申请一个数据包,然后将网卡中的数据填入数据包中;发送数据包时,协议栈的某层中会申请一个pbuf,并将相应的数据装入到数据区域,同时协议相关的首部信息也会被填到pbuf的预留数据区域中。
数据包申请函数有两个重要的参数,一是想申请的数据包类型,这在上面已经有所介绍,而一个参数就是该数据包是在协议栈的哪一层被申请的,分配函数会根据层次的不同,在pbuf数据区域前为相关的协议预留出首部空间,也即offset。
LwIP定义了四个层次,当数据包申请时,所处的层次不同,会导致预留空间的offset值不同,层次的定义是通过一个枚举类型pbuf_layer来实现的:
12345678typedef enum {PBUF_TRANSPORT, // 传输层PBUF_IP, // 网络层PBUF_LINK, // 链路层PBUF_RAW // 原始层,不预留任何空间}pbuf_layer;pbuf_alloc函数的源码如下:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859/*@layer: 指定该pbuf数据所处的层次,分配函数根据该值在pbuf数据区预留出首部空间 @length: 需要申请的数据区长度@type: pbuf的类型*/struct pbuf* pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type) {struct pbuf *p, *q, *r;u16_t offset; // 预留首部空间的长度s32_t rem_len; //还需要申请的数据空间长度offset = 0; switch (layer) {case PBUF_TRANSPORT:offset += PBUF_TRANSPORT_HLEN; // 若在传输层,则预留出TCP首部大小case PBUF_IP:offset += PBUF_IP_HLEN; // 若在网络层或传输层,则还需留出IP首部大小case PBUF_LINK: // 若在链路层或以上各层,则还需留出链路层首部offset += PBUF_LINK_HLEN; // 以太网中,为以太网头部大小,即14字节break;case PBUF_RAW:break;default: return NULL;}switch (type) {case PBUF_POOL: // 最麻烦,可能需要分配多个POOLp = memp_malloc(MEMP_PBUF_POOL);if (p == NULL) {return NULL;}p->type = type;p->next = NULL;p->payload = LWIP_MEM_ALIGN((void*)((u8_t*)p + (SIZEOF_STRUCT_PBUF + offset)));p->tot_len = length;p->len = LWIP_MIN(length, PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset));p->ref = 1;r = p;rem_len = length - p->len;while(rem_len > 0) {q = memp_alloc(MEMP_PBUF_POOL);if (q == NULL) {pbuf_free(q);return NULL;}q->type = type;q->flags = 0;q->next = NULL;r->next = q;q->tot_len = (u16_t)rem_len;q_len = LWIP_MIN((u16_t)rem_len, PBUF_POOL_BUFSIZE_ALIGNED);q->payload = (void*)((u8_t*)q + SIZEOF_STRUCT_PBUF);q->ref = 1;rem_len = q->len;r = q;}break;case PBUF_RAM:p = (struct pbuf*)mem_alloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));if (p == NULL) {return NULL;}...}}